/**
 * File:   modbus_server_view_model.c
 * Author: AWTK Develop Team
 * Brief:  modbus_server_view_model
 *
 * Copyright (c) 2023 - 2024  Guangzhou ZHIYUAN Electronics Co.,Ltd.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * License file for more details.
 *
 */

/**
 * History:
 * ================================================================
 * 2024-2-07 Li XianJing <xianjimli@hotmail.com> created
 *
 */

#include "tkc/utils.h"
#include "tkc/object.h"
#include "conf_io/conf_node.h"
#include "tkc/func_call_parser.h"

#include "hmi_model.h"
#include "hmi_service.h"
#include "base/main_loop.h"

#include "modbus_service_rtu.h"
#include "modbus_service_tcp.h"
#include "modbus_memory_default.h"
#include "modbus_server_channel.h"
#include "modbus_server_view_model.h"

#include "mvvm/mvvm.h"

#define MODBUS_SERVER_MODEL(obj) modbus_server_model_cast(obj)

typedef struct _modbus_server_model_t {
  tk_object_t object;
  /**
   * @property {char*} url
   * @annotation ["readable"]
   * URL。
   */
  char* url;
  /**
   * @property {uint8_t} unit_id
   * @annotation ["readable"]
   * 单元ID。
   */
  uint8_t unit_id;
  /**
   * @property {modbus_memory_t*} memory
   * @annotation ["readable"]
   * 内存。
   */
  modbus_memory_t* memory;
  /**
   * @property {tk_object_t*} variables
   * @annotation ["readable"]
   * 变量。
   */
  tk_object_t* variables;
} modbus_server_model_t;

static ret_t idle_notify_updated(const idle_info_t* info);
static modbus_server_model_t* modbus_server_model_cast(tk_object_t* obj);

static modbus_server_channel_t* modbus_server_find_channel(modbus_server_model_t* agent,
                                                                const char* name) {
  modbus_memory_default_t* memory = NULL;
  return_value_if_fail(agent != NULL && name != NULL, NULL);
  return_value_if_fail(agent->memory != NULL, NULL);

  memory = (modbus_memory_default_t*)(agent->memory);
  if (tk_str_eq(name, MODBUS_SERVER_CHANNEL_BITS)) {
    return memory->bits;
  } else if(tk_str_eq(name, MODBUS_SERVER_CHANNEL_INPUT_BITS)) {
    return memory->input_bits;
  } else if (tk_str_eq(name, MODBUS_SERVER_CHANNEL_REGISTERS)) {
    return memory->registers;
  } else if(tk_str_eq(name, MODBUS_SERVER_CHANNEL_INPUT_REGISTERS)) {
    return memory->input_registers;
  }

  return NULL;
}

ret_t modbus_server_view_model_parse_prop(tk_object_t* obj, const char* name, modbus_prop_t* prop) {
  modbus_server_channel_t* channel = NULL;
  modbus_server_model_t* agent = MODBUS_SERVER_MODEL(obj);
  return_value_if_fail(agent != NULL && name != NULL && prop != NULL, RET_BAD_PARAMS);

  return_value_if_fail(modbus_prop_parse(prop, agent->variables, name) == RET_OK, RET_BAD_PARAMS);
  
  channel = modbus_server_find_channel(agent, prop->channel_name);
  return_value_if_fail(channel != NULL, RET_NOT_FOUND);

  prop->buffer = channel->data;
  prop->buffer_length = channel->bytes;

  return_value_if_fail(prop->buffer != NULL, RET_BAD_PARAMS);

  return RET_OK;
}

static ret_t modbus_server_load_channels(modbus_server_model_t* agent, conf_node_t* node) {
  modbus_server_channel_t* bits = NULL;
  modbus_server_channel_t* input_bits = NULL;
  modbus_server_channel_t* registers = NULL;
  modbus_server_channel_t* input_registers = NULL;
  conf_node_t* iter = conf_node_get_first_child(node);
  return_value_if_fail(agent != NULL && node != NULL, RET_BAD_PARAMS);

  while (iter != NULL) {
    modbus_server_channel_t* channel = modbus_server_channel_create_with_conf(iter);
    return_value_if_fail(channel != NULL, RET_OOM);

    if (tk_str_ieq(channel->name, MODBUS_SERVER_CHANNEL_BITS)) {
      bits = channel;
    } else if (tk_str_ieq(channel->name, MODBUS_SERVER_CHANNEL_INPUT_BITS)) {
      input_bits = channel;
    } else if (tk_str_ieq(channel->name, MODBUS_SERVER_CHANNEL_REGISTERS)) {
      registers = channel;
    } else if (tk_str_ieq(channel->name, MODBUS_SERVER_CHANNEL_INPUT_REGISTERS)) {
      input_registers = channel;
    } else {
      log_debug("not supported channel name:%s\n", channel->name);
    }

    iter = iter->next;
  }

  agent->memory = modbus_memory_default_create(bits, input_bits, registers, input_registers);

  return RET_OK;
}

static ret_t modbus_server_load_variables(modbus_server_model_t* agent, conf_node_t* node) {
  value_t v;
  conf_node_t* iter = conf_node_get_first_child(node);

  while (iter != NULL) {
    const char* name = conf_node_get_name(iter);

    if (conf_node_get_value(iter, &v) == RET_OK) {
      log_debug("%s = %s\n", name, value_str(&v));
      tk_object_set_prop_str(agent->variables, name, value_str(&v));
    }

    iter = iter->next;
  }

  return RET_OK;
}

static ret_t modbus_server_load_conf(modbus_server_model_t* agent, conf_doc_t* doc) {
  conf_node_t* node = NULL;
  return_value_if_fail(agent != NULL && doc != NULL, RET_BAD_PARAMS);

  agent->url = tk_strdup(conf_doc_get_str(doc, "url", NULL));
  agent->unit_id = conf_doc_get_int(doc, "unit_id", 0xff);
  return_value_if_fail(agent->url != NULL, RET_BAD_PARAMS);

  node = conf_node_find_child(doc->root, "channels");
  if (node != NULL) {
    modbus_server_load_channels(agent, node);
  }

  node = conf_node_find_child(doc->root, "variables");
  if (node != NULL) {
    modbus_server_load_variables(agent, node);
  }

  return RET_OK;
}

static ret_t modbus_server_model_set_prop(tk_object_t* obj, const char* name, const value_t* v) {
  modbus_prop_t prop;
  return_value_if_fail(obj != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);

  if (tk_str_ieq(name, MODBUS_SERVER_MODEL_PROP_URL)) {
    return RET_NOT_FOUND;
  } else if (tk_str_ieq(name, MODBUS_SERVER_MODEL_PROP_UNIT_ID)) {
    return RET_NOT_FOUND;
  }

  if (modbus_server_view_model_parse_prop(obj, name, &prop) == RET_OK) {
    return modbus_prop_set_value(&prop, v);
  } else {
    log_debug("modbus_server_view_model_parse_prop failed:%s\n", name);
  }

  return RET_NOT_FOUND;
}

static ret_t modbus_server_model_get_prop(tk_object_t* obj, const char* name, value_t* v) {
  modbus_prop_t prop;
  modbus_server_model_t* agent = MODBUS_SERVER_MODEL(obj);
  return_value_if_fail(obj != NULL && name != NULL && v != NULL, RET_BAD_PARAMS);

  if (tk_str_ieq(name, MODBUS_SERVER_MODEL_PROP_URL)) {
    value_set_str(v, agent->url);
    return RET_OK;
  } else if (tk_str_ieq(name, MODBUS_SERVER_MODEL_PROP_UNIT_ID)) {
    value_set_int(v, agent->unit_id);
    return RET_OK;
  }

  if (modbus_server_view_model_parse_prop(obj, name, &prop) == RET_OK) {
    return modbus_prop_get_value(&prop, v);
  } else {
    log_debug("modbus_server_view_model_parse_prop failed:%s\n", name);
  }

  return RET_NOT_FOUND;
}

static ret_t modbus_server_model_on_destroy(tk_object_t* obj) {
  modbus_server_model_t* agent = MODBUS_SERVER_MODEL(obj);
  return_value_if_fail(agent != NULL, RET_BAD_PARAMS);

  TKMEM_FREE(agent->url);
  TK_OBJECT_UNREF(agent->variables);
  if (agent->memory != NULL) {
    modbus_memory_destroy(agent->memory);
  }

  return RET_OK;
}

static bool_t modbus_server_model_can_exec(tk_object_t* obj, const char* name, const char* args) {
  return FALSE;
}

static ret_t modbus_server_model_exec(tk_object_t* obj, const char* name, const char* args) {
  return RET_NOT_FOUND;
}

static const object_vtable_t s_modbus_server_vtable = {
    .type = "modbus_server",
    .desc = "modbus_server",
    .size = sizeof(modbus_server_model_t),
    .is_collection = FALSE,
    .on_destroy = modbus_server_model_on_destroy,
    .get_prop = modbus_server_model_get_prop,
    .set_prop = modbus_server_model_set_prop,
    .can_exec = modbus_server_model_can_exec,
    .exec = modbus_server_model_exec,
};

static ret_t modbus_server_model_on_request(void* ctx, event_t* evt) {
  emitter_dispatch_simple_event(EMITTER(ctx), EVT_PROPS_CHANGED);
  return RET_OK;
}

static ret_t modbus_server_model_start_service(modbus_server_model_t* agent) {
  const char* url = NULL;
  uint8_t unit_id = 0;
  modbus_memory_t* memory = NULL;
  modbus_memory_default_t* default_memory = NULL;
  event_source_manager_t* esm = main_loop_get_event_source_manager(main_loop());
  return_value_if_fail(agent != NULL && esm != NULL, RET_BAD_PARAMS);

  url = agent->url;
  unit_id = agent->unit_id;
  return_value_if_fail(url != NULL, RET_BAD_PARAMS);

  memory = agent->memory;
  return_value_if_fail(memory != NULL, RET_OOM);

  default_memory = (modbus_memory_default_t*)memory;
  emitter_on(default_memory->emitter, EVT_PROPS_CHANGED, modbus_server_model_on_request, agent);

  if (tk_str_start_with(url, STR_SCHEMA_RTU_OVER_TCP) || tk_str_start_with(url, STR_SCHEMA_TCP)) {
    const char* p = strrchr(url, ':');
    int port = p != NULL ? tk_atoi(p + 1) : 502;
    modbus_proto_t proto =
        tk_str_start_with(url, STR_SCHEMA_RTU_OVER_TCP) ? MODBUS_PROTO_RTU : MODBUS_PROTO_TCP;
    return modbus_service_tcp_start(esm, memory, port, proto, unit_id);
  } else {
    return modbus_service_rtu_start(esm, memory, url, unit_id);
  }

  return RET_OK;
}

static tk_object_t* modbus_server_model_create(conf_doc_t* doc) {
  tk_object_t* obj = NULL;
  modbus_server_model_t* agent = NULL;
  return_value_if_fail(doc != NULL, NULL);

  obj = object_create(&s_modbus_server_vtable);
  return_value_if_fail(obj != NULL, NULL);
  agent = (modbus_server_model_t*)obj;

  agent->variables = object_default_create();
  if (modbus_server_load_conf(agent, doc) == RET_OK) {
    modbus_server_model_start_service(agent);
    return obj;
  } else {
    TK_OBJECT_UNREF(obj);
  }

  return NULL;
}

static modbus_server_model_t* modbus_server_model_cast(tk_object_t* obj) {
  return_value_if_fail(obj != NULL, NULL);
  return_value_if_fail(obj->vt == &s_modbus_server_vtable, NULL);

  return (modbus_server_model_t*)obj;
}

tk_object_t* modbus_server_view_model_create_with_name(const char* name) {
  conf_doc_t* doc = NULL;
  tk_object_t* obj = NULL;
  char config_name[MAX_PATH + 1] = {0};
  tk_snprintf(config_name, MAX_PATH, "%s.json", name);
  doc = hmi_model_load_conf_from_asset(config_name);

  if (doc != NULL) {
    obj = modbus_server_model_create(doc);
    conf_doc_destroy(doc);
    tk_object_set_name(obj, name);
    return obj;
  }

  return NULL;
}

static view_model_t* s_modbus_view_model;

view_model_t* modbus_server_view_model_create(navigator_request_t* req) {
  if (s_modbus_view_model == NULL) {
    tk_object_t* obj = modbus_server_view_model_create_with_name("modbus");
    s_modbus_view_model = view_model_object_wrapper_create(obj);
    tk_object_set_name(TK_OBJECT(s_modbus_view_model), "modbus");
    TK_OBJECT_UNREF(obj);
  }
  return_value_if_fail(s_modbus_view_model != NULL, NULL);

  tk_object_ref(TK_OBJECT(s_modbus_view_model));
  return s_modbus_view_model;
}

ret_t modbus_server_view_model_deinit(void) {
  TK_OBJECT_UNREF(s_modbus_view_model);
  return RET_OK;
}

ret_t modbus_server_view_model_init(void) {
  return view_model_factory_register("modbus_server", modbus_server_view_model_create);
}
